﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Windows.Forms;
using Kyoh.Lib.Collections;
using Kyoh.Lib.Component.Globalization;

namespace Kyoh.Lib.Component.Traces
{
    /// <summary>トレース出力を管理するコンポーネントを定義します。</summary>
    public class TraceCollection : System.ComponentModel.Component, IList<TraceData>, ITypedList, IBindingListView
    {
        private readonly TraceDataTable _traces;
        private readonly DataView _dataView;
        private readonly IBindingListView _listView;
        private readonly IList _list;

        /// <summary><see cref="TraceCollection"/> クラスの新しいインスタンスを初期化します。</summary>
        public TraceCollection()
        {
            _traces = new TraceDataTable();
            _dataView = new DataView(_traces);
            _list = _dataView;
            _listView = _dataView;

            Listener = new TraceCollectionListener(this);
        }

        /// <summary>Dispose</summary>
        /// <param name="disposing">明示的に呼び出す場合は <c>true</c>。それ以外の場合は <c>false</c>。</param>
        protected override void Dispose(bool disposing)
        {
            try
            {
                Debug = false;
            }
            catch
            {
            }
            try
            {
                Trace = false;
            }
            catch
            {
            }
            if (disposing)
                Listener.Dispose();
            base.Dispose(disposing);
        }

        internal TraceData NewTraceData()
        {
            return _traces.NewTraceData();
        }

        internal TraceData AddTraceData(int id, TraceEventType eventType, string message, string detail, string dateTime,
                                        string timestamp, string processId, string threadId, string source,
                                        string callstack, Guid relatedActivityId, string logicalOperations)
        {
            return _traces.AddTraceData(id, eventType, message, detail, dateTime, timestamp, processId, threadId, source,
                                        callstack, relatedActivityId, logicalOperations);
        }

        #region リスナ

        /// <summary>出力先としてアタッチしている <see cref="Control"/> オブジェクトを取得・設定します。</summary>
        [CategoryFromResource(typeof (Properties.TraceManagerCategory), "Trace")]
        [DescriptionFromResource(typeof (Properties.TraceManagerDescription), "AttachedControl")]
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [DefaultValue(null)]
        public Control AttachedControl
        {
            get { return Listener.AttachedControl; }
            set { Listener.AttachControl(value); }
        }

        /// <summary>出力先としてアタッチしている <see cref="Control"/> クラスの種類を示す <see cref="TraceControlType"/> 値を取得します。</summary>
        [CategoryFromResource(typeof (Properties.TraceManagerCategory), "Trace")]
        [DescriptionFromResource(typeof (Properties.TraceManagerDescription), "AttachedControlType")]
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [DefaultValue(typeof (TraceControlType), "None")]
        public TraceControlType AttachedControlType
        {
            get { return Listener.AttachedControlType; }
        }

        /// <summary>トレースリスナに登録する <see cref="TraceCollectionListener"/> オブジェクトを取得します。</summary>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public TraceCollectionListener Listener { get; private set; }

        private bool _mTrace;

        /// <summary><see cref="System.Diagnostics.Trace"/> クラスのリスナに登録するかどうかを取得・設定します。</summary>
        [CategoryFromResource(typeof (Properties.TraceManagerCategory), "Trace")]
        [DescriptionFromResource(typeof (Properties.TraceManagerDescription), "Trace")]
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [DefaultValue(false)]
        public bool Trace
        {
            get { return _mTrace; }
            set
            {
                if (_mTrace && !value)
                    System.Diagnostics.Trace.Listeners.Remove(Listener);
                else if (!_mTrace && value)
                    System.Diagnostics.Trace.Listeners.Add(Listener);
                _mTrace = value;
            }
        }

        private bool _mDebug;

        /// <summary><see cref="System.Diagnostics.Debug"/> クラスのリスナに登録するかどうかを取得・設定します。</summary>
        [CategoryFromResource(typeof (Properties.TraceManagerCategory), "Trace")]
        [DescriptionFromResource(typeof (Properties.TraceManagerDescription), "Debug")]
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [DefaultValue(false)]
        public bool Debug
        {
            get { return _mDebug; }
            set
            {
#if DEBUG
                if (_mDebug && !value)
                    System.Diagnostics.Debug.Listeners.Remove(Listener);
                else if (!_mDebug && value)
                    System.Diagnostics.Debug.Listeners.Add(Listener);
#endif
                _mDebug = value;
            }
        }

        /// <summary>トレース出力に書き込むトレース データ オプションを取得・設定します。</summary>
        public TraceOptions TraceOutputOptions
        {
            get { return Listener.TraceOutputOptions; }
            set { Listener.TraceOutputOptions = value; }
        }

        #endregion

        #region IBindingList メンバー

        #region 検索機能

        /// <summary>検索機能をサポートするかどうかを取得します。</summary>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public bool SupportsSearching
        {
            get { return true; }
        }

        /// <summary>検索に使用するインデックスを追加します。</summary>
        /// <param name="property">インデックスとして使用するプロパティを示す <see cref="PropertyDescriptor"/> オブジェクト。</param>
        public void AddIndex(PropertyDescriptor property)
        {
            _listView.AddIndex(property);
        }

        /// <summary>検索に使用するインデックスを削除します。</summary>
        /// <param name="property">インデックスとして使用を中止するプロパティを示す <see cref="PropertyDescriptor"/> オブジェクト。</param>
        public void RemoveIndex(PropertyDescriptor property)
        {
            _listView.RemoveIndex(property);
        }

        /// <summary>指定されたプロパティに対して、指定された値の検索を行います。</summary>
        /// <param name="property">検索対象のプロパティを示す <see cref="PropertyDescriptor"/> オブジェクト。</param>
        /// <param name="key">検索に使用する値。</param>
        /// <returns>検索結果のインデックス。</returns>
        public int Find(PropertyDescriptor property, object key)
        {
            return _listView.Find(property, key);
        }

        #endregion

        #region 編集機能

        /// <summary>ユーザによる内容の編集を許可するかどうかを取得します。</summary>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public bool AllowEdit
        {
            get { return false; }
        }

        /// <summary>ユーザによる新しい要素の追加を許可するかどうかを取得します。</summary>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public bool AllowNew
        {
            get { return false; }
        }

        /// <summary>ユーザによる要素の削除を許可するかどうかを取得します。</summary>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public bool AllowRemove
        {
            get { return false; }
        }

        /// <summary>要素の追加を行います。（サポートされていません）</summary>
        /// <exception cref="NotSupportedException">このクラスでは、常にこの例外が発生します。</exception>
        /// <returns>追加したオブジェクト。当該メソッドはサポートされていないため、値は返りません。</returns>
        public object AddNew()
        {
            throw new NotSupportedException();
        }

        #endregion

        #region ListChanged イベント管理

        /// <summary>要素の変更通知をサポートするかどうかを取得します。</summary>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public bool SupportsChangeNotification
        {
            get { return true; }
        }

        /// <summary>要素の変更が行われた際に発生するイベントを追加・削除します。</summary>
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        public event ListChangedEventHandler ListChanged
        {
            add { _listView.ListChanged += value; }
            remove { _listView.ListChanged -= value; }
        }

        #endregion

        #region ソート

        private GenericMultiSorter<TraceData> _sorter = new GenericMultiSorter<TraceData>();

        /// <summary><see cref="TraceCollection"/> オブジェクトがソート機能をサポートするかどうかを取得します。</summary>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public bool SupportsSorting
        {
            get { return true; }
        }

        /// <summary>指定されたプロパティによって、指定された順序（昇順・降順）でのソートを行います。</summary>
        /// <param name="property">ソート基準として使用するプロパティを示す <see cref="PropertyDescriptor"/> オブジェクト。</param>
        /// <param name="direction">ソートの順序を示す <see cref="ListSortDirection"/> 列挙子の値。</param>
        public void ApplySort(PropertyDescriptor property, ListSortDirection direction)
        {
            lock (SyncRoot)
            {
                _listView.ApplySort(property, direction);
            }
        }

        /// <summary>ソートを解除します。</summary>
        public void RemoveSort()
        {
            lock (SyncRoot)
            {
                _listView.RemoveSort();
            }
        }

        /// <summary>ソート済みかどうかを示す値を取得します。</summary>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public bool IsSorted
        {
            get { return _listView.IsSorted; }
        }

        /// <summary>ソート順序（昇順・降順）を取得します。</summary>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public ListSortDirection SortDirection
        {
            get { return _listView.SortDirection; }
        }

        /// <summary>ソート基準に使用しているプロパティを示す <see cref="PropertyDescriptor"/> オブジェクトを取得します。</summary>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public PropertyDescriptor SortProperty
        {
            get { return _listView.SortProperty; }
        }

        #endregion

        #endregion

        #region ITypedList メンバー

        /// <summary>データをバインドするために使用される各項目のプロパティを表す <see cref="PropertyDescriptorCollection"/> を返します。</summary>
        /// <param name="listAccessors">リスト名を返す対象の <see cref="PropertyDescriptor"/> オブジェクトの配列。 この値は、<c>null</c> でもかまいません。 </param>
        /// <returns>データをバインドするために使用される各項目のプロパティを表す <see cref="PropertyDescriptorCollection"/>。</returns>
        public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
        {
            var props = TypeDescriptor.GetProperties(typeof (TraceData)).Sort(new PropertyDescriptorComparer());
            for (int i = 0; i < props.Count;)
            {
                switch (props[i].Name)
                {
                    case "HasErrors":
                    case "Table":
                    case "RowState":
                    case "RowError":
                        props.RemoveAt(i);
                        break;
                    default:
                        i++;
                        break;
                }
            }
            return props;
        }

        /// <summary>リストの名前を返します。</summary>
        /// <param name="listAccessors">リスト名を返す対象の <see cref="PropertyDescriptor"/> オブジェクトの配列。 この値は、<c>null</c> でもかまいません。 </param>
        /// <returns><c>null</c></returns>
        public string GetListName(PropertyDescriptor[] listAccessors)
        {
            return null;
        }

        #endregion

        #region TraceData の追加・削除

        /// <summary><see cref="TraceData"/> オブジェクトを追加します。</summary>
        /// <param name="item">追加する <see cref="TraceData"/> オブジェクト。</param>
        [DebuggerStepThrough]
        protected internal virtual void Add(TraceData item)
        {
            item.Index = _traces.Count + 1;
            _traces.AddTraceData(item);
        }

        /// <summary>例外をメッセージとして出力します。</summary>
        /// <param name="exc">出力する例外。</param>
        public void Exception(Exception exc)
        {
            Listener.Exception(exc);
        }

        /// <summary>内部で管理している<see cref="TraceData"/> オブジェクトをすべて削除します。</summary>
        [DebuggerStepThrough]
        public void Clear()
        {
            _traces.Clear();
            Listener.Clear();
        }

        /// <summary><see cref="TraceData"/> オブジェクトを削除します。</summary>
        /// <param name="item">削除対象の <see cref="TraceData"/> オブジェクト。</param>
        /// <returns>リスト内に指定された <see cref="TraceData"/> オブジェクトが存在し、削除された場合は <c>true</c>。それ以外の場合は <c>false</c>。</returns>
        public bool Remove(TraceData item)
        {
            _traces.RemoveTraceData(item);
            return true;
        }

        void IList.Remove(object value)
        {
            var traceData = value as TraceData;
            if (traceData != null)
                Remove(traceData);
        }

        /// <summary>指定されたインデックスの <see cref="TraceData"/> オブジェクトを削除します。</summary>
        /// <param name="index">削除する <see cref="TraceData"/> オブジェクトのインデックス。</param>
        public void RemoveAt(int index)
        {
            Remove(_traces[index]);
        }

        #endregion

        #region IList<TraceData> メンバー

        void ICollection<TraceData>.Add(TraceData item)
        {
            _traces.AddTraceData(item);
        }

        int IList.Add(object value)
        {
            return _list.Add(value);
        }

        /// <summary>指定された <see cref="TraceData"/> オブジェクトが存在するかどうかを取得します。</summary>
        /// <param name="item">存在するかどうかチェックする <see cref="TraceData"/> オブジェクト。</param>
        /// <returns>存在する場合は <c>true</c>。それ以外の場合は <c>false</c>。</returns>
        public bool Contains(TraceData item)
        {
            return _list.Contains(item);
        }

        bool IList.Contains(object value)
        {
            return _list.Contains(value);
        }

        /// <summary>保持している <see cref="TraceData"/> オブジェクトを配列にコピーします。</summary>
        /// <param name="array">コピー対象の配列。</param>
        /// <param name="arrayIndex">コピー対象の配列のオフセット値。</param>
        public void CopyTo(TraceData[] array, int arrayIndex)
        {
            _list.CopyTo(array, arrayIndex);
        }

        void ICollection.CopyTo(Array array, int index)
        {
            _list.CopyTo(array, index);
        }

        /// <summary>保持している <see cref="TraceData"/> オブジェクトの個数を取得します。</summary>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public int Count
        {
            get { return _dataView.Count; }
        }

        /// <summary>指定された <see cref="TraceData"/> オブジェクトのインデックスを取得します。</summary>
        /// <param name="item">検索する <see cref="TraceData"/> オブジェクト。</param>
        /// <returns>指定された <see cref="TraceData"/> オブジェクトが存在する場合は、そのインデックス値。それ以外の場合は <c>-1</c>。</returns>
        public int IndexOf(TraceData item)
        {
            return _list.IndexOf(item);
        }

        int IList.IndexOf(object value)
        {
            return _list.IndexOf(value);
        }

        /// <summary>要素の挿入を行います。</summary>
        /// <param name="index">挿入対象のインデックス。</param>
        /// <param name="item">挿入する <see cref="TraceData"/> オブジェクト。</param>
        public void Insert(int index, TraceData item)
        {
            _list.Insert(index, item);
        }

        void IList.Insert(int index, object value)
        {
            _list.Insert(index, value);
        }

        /// <summary>コレクションが固定長かどうかを取得します。</summary>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public bool IsFixedSize
        {
            get { return true; }
        }

        /// <summary>コレクションが読み取り専用かどうかを取得します。</summary>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public bool IsReadOnly
        {
            get { return true; }
        }

        /// <summary>コレクションが同期されているかどうかを取得します。</summary>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public bool IsSynchronized
        {
            get { return _list.IsSynchronized; }
        }

        /// <summary>コレクションへの変更に使用するロック用オブジェクトを取得します。</summary>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public object SyncRoot
        {
            get { return _list.SyncRoot; }
        }

        /// <summary>コレクションの内容を列挙します。</summary>
        /// <returns>コレクションの内容を列挙する <see cref="IEnumerator{T}"/></returns>
        public IEnumerator<TraceData> GetEnumerator()
        {
            foreach (DataRowView row in _dataView)
                yield return (TraceData) row.Row;
        }

        /// <summary>
        /// コレクションを反復処理する列挙子を返します。
        /// </summary>
        /// <returns>
        /// コレクションを反復処理するために使用できる <see cref="T:System.Collections.IEnumerator"/> オブジェクト。
        /// </returns>
        /// <filterpriority>2</filterpriority>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return _dataView.GetEnumerator();
        }

        /// <summary>指定されたインデックスの要素を取得します。（設定はサポートしていません）</summary>
        /// <param name="index">取得対象のインデックス。</param>
        public TraceData this[int index]
        {
            get { return (TraceData) _dataView[index].Row; }
            set { throw new NotSupportedException(); }
        }

        /// <summary>
        /// 指定したインデックスにある要素を取得または設定します。
        /// </summary>
        /// <returns>
        /// 指定したインデックスにある要素。
        /// </returns>
        /// <param name="index">取得または設定する要素の、0 から始まるインデックス番号。</param><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> が <see cref="T:System.Collections.IList"/> の有効なインデックスではありません。</exception><exception cref="T:System.NotSupportedException">このプロパティが設定されていますが、<see cref="T:System.Collections.IList"/> が読み取り専用です。</exception><filterpriority>2</filterpriority>
        object IList.this[int index]
        {
            get { return _dataView[index].Row; }
            set { throw new NotSupportedException(); }
        }

        #endregion

        #region IBindingListView メンバー

        /// <summary>
        /// 指定した <see cref="T:System.ComponentModel.ListSortDescriptionCollection"/> に基づいて、データ ソースを並べ替えます。
        /// </summary>
        /// <param name="sorts">データ ソースに適用する並べ替えを格納する <see cref="T:System.ComponentModel.ListSortDescriptionCollection"/>。</param>
        public void ApplySort(ListSortDescriptionCollection sorts)
        {
            _listView.ApplySort(sorts);
        }

        /// <summary>
        /// データ ソースによって返された項目のコレクションから項目を除外するときに使用するフィルタを取得または設定します。
        /// </summary>
        /// <returns>
        /// データ ソースによって返される項目のコレクションに含まれる項目をフィルタ処理するときに使用する文字列。 
        /// </returns>
        public string Filter
        {
            get { return _listView.Filter; }
            set { _listView.Filter = value; }
        }

        /// <summary>
        /// データ ソースに適用されている現在のフィルタを削除します。
        /// </summary>
        public void RemoveFilter()
        {
            _listView.RemoveFilter();
        }

        /// <summary>
        /// 現在データ ソースに適用されている並べ替えに関する説明のコレクションを取得します。
        /// </summary>
        /// <returns>
        /// 現在データ ソースに適用されている <see cref="T:System.ComponentModel.ListSortDescriptionCollection"/>。
        /// </returns>
        public ListSortDescriptionCollection SortDescriptions
        {
            get { return _listView.SortDescriptions; }
        }

        /// <summary>
        /// データ ソースが高度な並べ替えをサポートしているかどうかを示す値を取得します。 
        /// </summary>
        /// <returns>
        /// データ ソースが高度な並べ替えをサポートしている場合は true。それ以外の場合は false。 
        /// </returns>
        public bool SupportsAdvancedSorting
        {
            get { return _listView.SupportsAdvancedSorting; }
        }

        /// <summary>
        /// データ ソースがフィルタ処理をサポートしているかどうかを示す値を取得します。 
        /// </summary>
        /// <returns>
        /// データ ソースがフィルタ処理をサポートしている場合は true。それ以外の場合は false。 
        /// </returns>
        public bool SupportsFiltering
        {
            get { return _listView.SupportsFiltering; }
        }

        #endregion
    }
}